/*
 * page.c
 *
 *  Created on: 2012-6-27
 *  Author: monkey
 */

#include <vm/page.h>
#include "threads/thread.h"

#define SPTE_size (sizeof(struct SPTE))
#define SPT_page_contain (PGSIZE/SPTE_size)


void SPT_init(struct sup_page_table *SPT)
{
    list_init(&(SPT->table));
    SPT->allocated_page = NULL;
    SPT->free_offset = 0;
    lock_init(&SPT->lock);
    //SPT->next_stack = ((uint8_t *)PHYS_BASE) - PGSIZE;
}

enum sup_page_status
page_map(struct sup_page_table *SPT,uintptr_t page_n,uintptr_t frame_n,bool writable)
{
    struct SPTE *old;
    struct SPTE *new_one;

    lock_acquire(&SPT->lock);//lock

    old = SPTE_find(SPT,page_n);

    if (old == NULL)
    {
        if (SPT->allocated_page == NULL)
            SPT->allocated_page = palloc_get_page(0);

        if (SPT->free_offset >= SPT_page_contain)
        {
            SPT->allocated_page = palloc_get_page(0);
            SPT->free_offset = 0;
        }

        if (SPT->allocated_page == NULL)
        {
            lock_release(&SPT->lock);
            return SP_OUT_OF_MEM;
        }

        new_one = SPT->allocated_page + SPTE_size * SPT->free_offset;
        SPT->free_offset++;

        //lock_init(&new_one->lock);
        new_one->frame_number = frame_n;
        new_one->page_number = page_n;
        //new_one->writable = writable;
        list_push_back(&SPT->table,&new_one->elem);
        pagedir_set_page(thread_current()->pagedir,pgno_to_va(page_n),pgno_to_va(frame_n),writable);

        lock_release(&SPT->lock);
        return SP_SUCCESS;
    }
    else if (old->frame_number == 0)
    {
        old->frame_number = frame_n;
        //old->writable = writable;
        pagedir_set_page(thread_current()->pagedir,pgno_to_va(page_n),pgno_to_va(frame_n),writable);
        lock_release(&SPT->lock);
        return SP_SUCCESS;
    }
    else
    {
        lock_release(&SPT->lock);
        return SP_MAPPED;
    }
}


bool page_unmap(struct thread *t,uintptr_t page_n)
{
    if (is_kernel_vaddr(pgno_to_va(page_n)))
    {
        printf ("not alow to unmap kernel addr\n");
        return false;
    }
    struct SPTE *target = SPTE_find(&t->SPT,page_n);

    lock_acquire(&t->SPT.lock);

    if (target == NULL)
    {
        lock_release(&t->SPT.lock);
        return false;
    }

    target->frame_number = 0;
    pagedir_clear_page(t->pagedir,pgno_to_va(page_n));
    lock_release(&t->SPT.lock);
    return true;
}

struct SPTE *
SPTE_find(struct sup_page_table *SPT,uintptr_t page_n)
{
    struct list_elem *e;
    struct SPTE *target;

    for (e=list_begin(&SPT->table); e!=list_end(&SPT->table); e=list_next(e))
    {
        target = list_entry(e,struct SPTE,elem);

        if (target->page_number == page_n)
            break;
    }

    if (e == list_end(&SPT->table))
        return NULL;
    else
        return target;
}

bool page_is_mapped(struct sup_page_table *SPT,uintptr_t page_n)
{
    struct SPTE *target = SPTE_find(SPT,page_n);

    if (target == NULL)
        return false;

    if (target->frame_number == 0)
        return false;

    else
        return true;
}
